#include "gen.h"
#include "errors.h"

#define MAX_ARGS 5

typedef GEN (*g_envelope) (GEN*);

static PyObject*
call_pari(PyObject** args, int num, g_envelope func) {
	pari_sp __av = avma;
	GEN gen[MAX_ARGS];
	for(int i = 0; i<num; i++) {
		gen[i] = gen_cast(args[i]);
		if(gen[i]==NULL) {
			avma = __av;
			return NULL;
		}			
	}
	CATCH_PARI {
		SET_ERROR;
		avma = __av;
		return NULL;
	} TRY_PARI {
		PyObject* res = (PyObject*) create_gen(func(gen));
		avma = __av;
		return res;
	} ENDCATCH_PARI
}

#define GEN_UNARY(gp_name, export_name) \
static PyObject* \
export_name(PyObject* obj) { \
	PyObject* args[]={obj}; \
	return call_pari(args, 1, &gp_name##_envelope); \
} 

#define GEN_BINARY(gp_name, export_name) \
static PyObject* \
export_name(PyObject* obj1, PyObject* obj2) { \
	PyObject* args[]={obj1, obj2}; \
	return call_pari(args, 2, &gp_name##_envelope); \
} 

#define FUNC_1(gp_name) \
static GEN gp_name##_envelope (GEN* g) { return gp_name(g[0]); };

#define FUNC_1_PREC(gp_name) \
static GEN gp_name##_envelope (GEN* g) { return gp_name(g[0], pari_real_prec); };

#define FUNC_2(gp_name) \
static GEN gp_name##_envelope (GEN* g) { return gp_name(g[0], g[1]); };

#define FUNC_2_PREC(gp_name) \
static GEN gp_name##_envelope (GEN* g) { return gp_name(g[0], g[1], pari_real_prec); };

FUNC_2(gadd) GEN_BINARY(gadd, gen_add)
FUNC_2(gsub) GEN_BINARY(gsub, gen_sub)
FUNC_2(gmul) GEN_BINARY(gmul, gen_mul)
FUNC_2(gdiv) GEN_BINARY(gdiv, gen_div)
FUNC_2(gmod) GEN_BINARY(gmod, gen_mod)
FUNC_2_PREC(gpow) GEN_BINARY(gpow, gen_pow)
FUNC_1(gneg) GEN_UNARY(gneg, gen_neg)
FUNC_1_PREC(gabs) GEN_UNARY(gabs, gen_abs)

static PyObject* gen_int(PyObject* _obj) {
	pari_sp __av = avma;
	PyGEN* obj = (PyGEN*) _obj;
	CATCH_PARI {
		SET_ERROR;
		avma = __av;
		return NULL;
	} TRY_PARI {
		PyObject* res = PyInt_FromLong(itos(obj->val));
		avma = __av;
		return res;
	} ENDCATCH_PARI
}

static PyObject* gen_float(PyObject* _obj) {
	pari_sp __av = avma;
	PyGEN* obj = (PyGEN*) _obj;
	CATCH_PARI {
		SET_ERROR;
		avma = __av;
		return NULL;
	} TRY_PARI {
		PyObject* res = PyFloat_FromDouble(rtodbl(obj->val));
		avma = __av;
		return res;
	} ENDCATCH_PARI
}

static PyObject* gen_pow_tern(PyObject* _obj1, PyObject* _obj2, PyObject*) {
	return gen_pow(_obj1, _obj2);
}

int gen_coerce(PyObject ** v, PyObject ** w) {
	PyObject* oldv = *v;
	if((*v)->ob_type != type_ptr) {
		PyObject* newv = (PyObject*) create_gen(gen_cast(*v));
		if(newv == NULL) {
			PyErr_Clear();
			return 1;
		}
		*v = newv;
	} else {
		Py_INCREF(*v);
	}
	if((*w)->ob_type != type_ptr) {
		PyObject* neww = (PyObject*) create_gen(gen_cast(*w));
		if(neww == NULL) {
			PyErr_Clear();
			Py_DECREF(*v);
			*v = oldv;
			return 1;
		}
		*w = neww;
	} else {
		Py_INCREF(*w);
	}
	return 0;
}

PyNumberMethods gen_as_number = {
    gen_add, // binaryfunc nb_add;
    gen_sub, //binaryfunc nb_subtract;
    gen_mul, //binaryfunc nb_multiply;
    gen_div, //binaryfunc nb_divide;
    gen_mod, //binaryfunc nb_remainder;
    0, //binaryfunc nb_divmod;
    gen_pow_tern, //ternaryfunc nb_power;
    gen_neg, //unaryfunc nb_negative;
    0, //unaryfunc nb_positive;
    gen_abs, //unaryfunc nb_absolute;
    0, //inquiry nb_nonzero;
    0, //unaryfunc nb_invert;
    0, //binaryfunc nb_lshift;
    0, //binaryfunc nb_rshift;
    0, //binaryfunc nb_and;
    0, //binaryfunc nb_xor;
    0, //binaryfunc nb_or;
    gen_coerce, //coercion nb_coerce;
    gen_int, //unaryfunc nb_int;
    0, //unaryfunc nb_long;
    gen_float, //unaryfunc nb_float;
    0, //unaryfunc nb_oct;
    0 //unaryfunc nb_hex;
};

static bool pari_div_on = false;
static binaryfunc old_div_int;
static binaryfunc old_div_long;

PyObject* gs_div_behavior(PyObject*, PyObject* args) { 
	if(PyTuple_Size(args)==0) { 
		return PyInt_FromLong(pari_div_on); 
	} else { 
		long arg = 0;
		if(!PyArg_ParseTuple(args, "l", &arg)) return NULL; 
		if(arg) {
			if(!pari_div_on) {
				old_div_int = PyInt_Type.tp_as_number->nb_divide;
				old_div_long = PyLong_Type.tp_as_number->nb_divide;
				PyInt_Type.tp_as_number->nb_divide = gen_div;
				PyLong_Type.tp_as_number->nb_divide = gen_div;
				pari_div_on = true;
			} 
		} else {
			if(pari_div_on) {
				PyInt_Type.tp_as_number->nb_divide = old_div_int;
				PyLong_Type.tp_as_number->nb_divide = old_div_long;
				pari_div_on = false;
			}
		}
		Py_INCREF(Py_None); 
		return Py_None; 
	}
}
